Skip to content

gh-159717: Use atomic groups for stars in glob.translate - #160580

Open
digit50 wants to merge 1 commit into
python:mainfrom
digit50:fix/glob-translate-atomic-groups
Open

digit50 wants to merge 1 commit into
python:mainfrom
digit50:fix/glob-translate-atomic-groups

Conversation

@digit50

@digit50 digit50 commented Oct 24, 2026 •

Copy link
Copy Markdown

closes #159717

glob.translate() kept only the parts list from fnmatch._translate() and dropped the star indices, so every * in a segment became a plain backtracking [^/]*. A failing match then took exponential time, which made PurePath.match, PurePath.full_match and Path.glob hang on short patterns such as '*a' * 8 + '*b'.

This change builds each segment from both the parts and the star indices and wraps every interior STAR fixed pair in an atomic group (?>[^/]*?fixed), the same rewrite fnmatch._join_translated_parts() uses. A pair is left as it was when its fixed part can match a separator. That happens only with a character class such as [!x] or [+-0], and there the atomic group could change the result: today *[!x]*x matches a/x because the class matches the /, and this change keeps that.

Tests:

  • test_glob.test_translate_slow and the pathlib test_full_match_slow and test_glob_slow are counterparts of test_slow_fnmatch that use the pattern from the issue against a 100 character name. Before this change none of them finished within a 30 second timeout; with it they take a few milliseconds.
  • test_translate_negated_class_matches_sep pins the current result for *[!x]*x.
  • The expected strings in the existing test_translate* tests are updated for the *.* segment, which now translates to (?>[^/]*?\.)[^/]*.

I also compared the old and new translation on 20,000 random patterns built from stars, literals, separators and character classes, with every combination of recursive, include_hidden and seps, against 30 random names each, and every result was the same.

glob.translate() dropped the star indices from fnmatch._translate(), so every star in a segment became a plain backtracking group and matching took exponential time in pathlib's match, full_match and glob. Each interior star and the fixed part after it are now wrapped in an atomic group, as fnmatch already does. Pairs whose fixed part can match a separator are left unchanged, so the set of matching names stays the same.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pathlib match, full_match and glob take exponential time on short patterns because glob.translate skips fnmatch's atomic-group rewrite

1 participant